#include <mh/renderer/d3d11.hpp>
#include <mh/memory.hpp>

// D3D11 렌더러 정의
namespace Mh
{
	RendererD3D11::RendererD3D11()
		:	Renderer( Renderer::TYPE_D3D11 )
		,	m_module( NULL )
		,	m_device( NULL )
		,	m_render_target( NULL )
		,	m_depth_stencil( NULL )
	{
	}

	RendererD3D11::~RendererD3D11()
	{
	}

	bool RendererD3D11::initialize( const Desc& desc )
	{
		if( !this->load_module() )
			return false;

		if ( !m_dxgi.initialize() )
			return false;

		if ( !m_d2d1.initialize() )
			return false;

		if( !this->create_device() )
			return false;

		if( !m_dxgi.create_swap_chain( m_device, desc.width, desc.height ) )
			return false;

		if( !this->create_view() )
			return false;

		{
			Viewport vp;
			vp.x = 0;
			vp.y = 0;
			vp.width = desc.width;
			vp.height = desc.height;
			vp.near_z = 0.0f;
			vp.far_z = 1.0f;

			this->set_viewport( vp );
			
			::D3D11_RECT rt;
			rt.left = 0;
			rt.top = 0;
			rt.right = desc.width;
			rt.bottom = desc.height;
			m_immediate_context->RSSetScissorRects( 1, &rt );
		}
		return true;
	}

	void RendererD3D11::finalize()
	{
		SafeRelease( &m_depth_stencil );
		SafeRelease( &m_render_target );
		SafeRelease( &m_immediate_context );
		SafeRelease( &m_device );

		if( m_module )
		{
			FreeLibrary( m_module );
			m_module = NULL;
		}
	}

	bool RendererD3D11::on_resize( uint width, uint height )
	{
		return m_dxgi.resize( width, height );
	}

	bool RendererD3D11::load_module()
	{
		// d3d11
		m_module = ::LoadLibrary( TEXT("D3D11.dll") );
		if ( !m_module )
			return false;		

		return true;
	}


	bool RendererD3D11::create_device()
	{
		typedef HRESULT (WINAPI *CreateDeviceFunc)(
			__in_opt IDXGIAdapter* pAdapter,
			D3D_DRIVER_TYPE DriverType,
			HMODULE Software,
			UINT Flags,
			__in_ecount_opt( FeatureLevels ) CONST D3D_FEATURE_LEVEL* pFeatureLevels,
			UINT FeatureLevels,
			UINT SDKVersion,
			__out_opt ID3D11Device** ppDevice,
			__out_opt D3D_FEATURE_LEVEL* pFeatureLevel,
			__out_opt ID3D11DeviceContext** ppImmediateContext );

		CreateDeviceFunc pf;
		HRESULT hr;

		// get function address
		{
			pf = (CreateDeviceFunc)::GetProcAddress( m_module, "D3D11CreateDevice" );
			if( !pf )
				return false;
		}

		// create device and immediate device context
		{
			::UINT flags = 0;
			::D3D_FEATURE_LEVEL feature_level;

			flags |= ::D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifdef	_DEBUG
			flags |= ::D3D11_CREATE_DEVICE_DEBUG;
			flags |= ::D3D11_CREATE_DEVICE_SWITCH_TO_REF;
#endif
			// DirectX에서는 디바이스 생성시 같은 DXGI 객체를 사용해야하며, 어댑터에 NULL이 아닌 값을 넣을때는 UNKNOWN으로 설정해야함
			// 즉, DXGI와 D3DDevice 생성을 분리하려면 DXGI팩토리에서 얻은 어댑터값과, TYPE_UNKNOWN을 사용해야함
			hr = (*pf)( m_dxgi.get_adapter(), ::D3D_DRIVER_TYPE_UNKNOWN, NULL, flags, NULL, 0, D3D11_SDK_VERSION, &m_device, &feature_level, &m_immediate_context );
			if( FAILED( hr ) )
				return false;
		}	

		return true;
	}

	bool RendererD3D11::create_view()
	{
		HRESULT hr;

		// redner target
		{
			Texture2D* pRenderTarget;
			hr = m_dxgi.get_swap_chain()->GetBuffer( 0, __uuidof(pRenderTarget), reinterpret_cast<void**>(&pRenderTarget) );
			if( FAILED(hr) )
				return false;

			hr = m_device->CreateRenderTargetView( pRenderTarget, NULL, &m_render_target );
			ULONG count = pRenderTarget->Release();
			assert( !count );
			if( FAILED(hr) )
				return false;
		}
		
		// depth, stencil view
		{
#if 0
			::D3D11_DEPTH_STENCIL_VIEW_DESC desc;
			desc.Format = ::DXGI_FORMAT_D24_UNORM_S8_UINT;
			desc.ViewDimension = ::D3D11_DSV_DIMENSION_TEXTURE2D;
			hr = m_device->CreateDepthStencilView( NULL, &desc, &m_depth_stencil );
			if( FAILED(hr) )
				return false;
#endif
		}

		// set views
		m_immediate_context->OMSetRenderTargets( 1, &m_render_target, m_depth_stencil );

		return true;
	}

	bool RendererD3D11::begin_render()
	{
		const FLOAT color[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
		m_immediate_context->ClearRenderTargetView( m_render_target, color );
		
		return true;
	}

	void RendererD3D11::end_render()
	{
		m_dxgi.get_swap_chain()->Present( 0, 0 );
	}

	void RendererD3D11::set_viewport( const Viewport& viewport )
	{
		::D3D11_VIEWPORT d3d11vp;
		d3d11vp.TopLeftX = static_cast<FLOAT>(viewport.x);
		d3d11vp.TopLeftY = static_cast<FLOAT>(viewport.y);
		d3d11vp.Width = static_cast<FLOAT>(viewport.width);
		d3d11vp.Height = static_cast<FLOAT>(viewport.height);
		d3d11vp.MinDepth = viewport.near_z;
		d3d11vp.MaxDepth = viewport.far_z;
		m_immediate_context->RSSetViewports( 1, &d3d11vp );
	}
}